Skip to content

fix: 工作区切换后历史图片落盘 + 按需读取 - #238

Merged
lishuceo merged 5 commits into
mainfrom
feat/claude-session-8dc376
May 17, 2026
Merged

fix: 工作区切换后历史图片落盘 + 按需读取#238
lishuceo merged 5 commits into
mainfrom
feat/claude-session-8dc376

Conversation

@lishuceo

Copy link
Copy Markdown
Owner

Summary

  • 修复 setup_workspace 切换工作区后,restart query 不重传多模态 images 导致历史聊天图片丢失的问题
  • 参照 PR feat: 大文本文件 lazy loading + 24h 文件缓存 #237 的 lazy-loading 模式,将当前消息图片 + 历史消息图片同步落盘到 /tmp/feishu-downloads/
  • 重启时把落盘路径以文本提示注入 prompt,agent 可按需通过 Read 工具加载查看
  • 新增 feishu_download_message_image MCP 工具,供父群图片 lazy loading 与新落盘路径按需读取

Changes

  1. src/feishu/tools/image.ts(新增)— feishu_download_message_image MCP 工具,30MB 上限,按 magic bytes 推导扩展名
  2. src/feishu/event-handler.ts
    • downloadHistoryImages 在压缩历史图前先把原图落盘,返回 savedImagePaths
    • parseMessage 收集当前消息图片落盘路径到 currentImagePaths
    • 新增 formatRestartImageHints 把所有落盘路径拼成 [历史聊天图片] 提示
    • workspace 切换重启时将提示拼到 effectivePrompt
  3. src/session/{types,queue}.tsQueueTaskenqueue() 透传 currentImagePaths

Test plan

  • npx tsc --noEmit — 无新增类型错误
  • npx vitest run src/feishu/__tests__/event-handler.test.ts — 55 passed(含新增 4 个 formatRestartImageHints 测试)
  • npx vitest run src/feishu/tools/__tests__/index.test.ts — 9 passed(已更新 image tool 注册计数)
  • 端到端:触发 setup_workspace 切换工作区后,验证 agent 仍能通过 Read 工具看到之前的图片

🤖 Generated with Claude Code

lishuceo and others added 3 commits May 17, 2026 16:37
按需下载飞书消息图片,参照 PR #237 的 file-cache 模式,为父群图片
lazy loading 与工作区切换后图片落盘提供按需读取能力。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
为工作区切换重启时图片路径注入做准备,让本次消息内已落盘的图片
路径能从 parseMessage 经队列流到 executeClaudeTask。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
setup_workspace 触发的 restart query 不重传多模态 images(避免
重复消耗 token),导致之前加载的图片在新工作区上下文中丢失。

修复:
- downloadHistoryImages 在下载历史图片时同步将原图落盘到 file-cache
- parseMessage 将当前消息图片落盘路径收集到 currentImagePaths
- 工作区切换重启时将所有落盘路径以文本提示注入 prompt,agent
  可按需通过 Read 工具加载查看

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lishuceo's task in 4m 38s —— View job


Claude PR Review — ⚠️ 1 Issue Found

Verdict: 整体设计合理 — 落盘+文本 hint 兜底是优雅方案,与 PR #237 lazy loading 模式一致。发现 1 个同类未覆盖场景。

  • Check previous review comments (无历史评论)
  • Read CLAUDE.md & 项目约定 (ESM .js 扩展、singleton 等均符合)
  • Review image.ts MCP 工具
  • Review event-handler.ts 改动
  • Review session queue/types 改动
  • Review 测试覆盖
  • Post inline comments & summary

🟡 主要发现

引用消息(quoted parent)图片在 restart 后丢失inline #3254304805

injectQuotedMessage (L1979-2058) 当 quoted root 是图片时下载并塞进 images(L1999-2009、L2055),但没有落盘,也没进入 restartImagePathsexecuteClaudeTask L2311 在 restartImagePaths 已构建完成(L2257)之后才调用 injectQuotedMessage,所以 quoted 图片在 workspace 切换 + restart 后会丢失 —— 与 PR 修复目标属同类 bug。

修复成本很低:让 injectQuotedMessage 同步调用 saveMessageFileToCache 并返回 savedPath,在 L2311 后追加到 restartImagePaths

✅ 设计/实现 Highlight

  • formatRestartImageHints 去重保序、空数组短路、注入 Read 工具 指引 — 清晰,单测覆盖 4 个分支
  • downloadHistoryImages 落盘失败用 logger.debug non-fatal 兜底,不阻塞主流程
  • 落盘用原图 buf(非压缩版),方便 agent restart 后 Read 原画质 — 与多模态压缩解耦合理
  • mediaTypeToExtdetectImageMediaType 配对,覆盖 jpeg/png/gif/webp + fallback
  • image.ts MCP 工具仿照 message.ts 的 file 工具结构对称、30MB 上限、magic bytes 探测 ext 合理
  • currentImagePathsQueueTaskenqueueexecuteClaudeTask 链路上参数传递无遗漏;executeDirectTask 不需此参数(direct 模式无 restart-on-workspace-change)

次要观察(不阻塞)

  • merge_forward 子消息没有提取/落盘图片(飞书 API 限制本身不支持下载子消息资源),与 PR 范围一致
  • mediaTypeToExtimage.tsdetectImageExt 是两套类似的 magic-bytes/类型映射,未来可抽到 file-cache.ts 或 image utils 共用 — 现在重复但可接受

测试

  • formatRestartImageHints 4 个测试覆盖 empty / single / dedupe / multiple
  • ✅ tool registration counts 已同步更新(10/11 等)
  • ⚠️ 未新增 parseMessage.currentImagePathsdownloadHistoryImages.savedImagePaths 的集成断言(手动 E2E 还在 PR 描述的 to-do 中)
    · Branch: feat/claude-session-8dc376

Comment thread src/feishu/event-handler.ts
injectQuotedMessage 下载引用消息中的图片时一并 saveMessageFileToCache,
在 executeClaudeTask 调用处把落盘路径合并进 restartImagePaths,
修复 reviewer 指出的:工作区切换 restart 后 quoted parent image 同样
会丢失的问题。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lishuceo's task in 1m 42s —— View job


Claude PR Review — ✅ Approved

Verdict: 1 previous issue fixed. No new issues found.

  • Check previous review comments & verify fix for r3254304805
  • Read CLAUDE.md & PR diff
  • Review new commit 96292bc (引用图片同步落盘 + 注入 restart 路径)
  • Reply & resolve fixed threads
  • Post summary

Verified fix — quoted parent image restart 同类 bug

Previous inline #3254304805 已修复(commit 96292bc):

  • injectQuotedMessage 签名扩展 savedImagePath?: stringsrc/feishu/event-handler.ts:1985),quoted image 下载并压缩后调用 saveMessageFileToCache 落盘原图(L2015-2020),try/catch 兜底落盘失败 → logger.debug non-fatal,与 downloadHistoryImages 的兜底策略一致。
  • executeClaudeTask L2328 在 injectQuotedMessage 调用后立即把 savedImagePath push 进 restartImagePaths,时序正确:发生在 L2536 formatRestartImageHints 之前。
  • workspace 切换 restart 时 quoted parent image 可通过 Read 工具按需加载,与 PR 主目标(history images)完全对齐。

设计 / 实现 Highlight

  • 修复点小而精:18 行新增、零侵入式扩展,未触碰多模态压缩 / existingImages 合并逻辑
  • 复用 mediaTypeToExt + saveMessageFileToCache,与 downloadHistoryImages 风格统一
  • 落盘失败 try/catch 与现有 history image 落盘容错对称,不阻塞主流程

次要观察(不阻塞)

  • 测试侧未新增 injectQuotedMessage 返回 savedImagePath 的集成断言。由于 injectQuotedMessage 涉及 feishuClient.getMessageById + downloadMessageImage + compressImage 三层 mock,单测搭建成本较高,PR 描述的 E2E to-do 是合理的兜底
  • merge_forward 引用消息子图片仍未落盘(飞书 API 本身不支持子消息资源下载,与 PR 范围一致)

· Branch: feat/claude-session-8dc376

96292bc 修复加针对性测试:
- 验证下载 quoted 图片后调用 saveMessageFileToCache 并返回 savedImagePath
- 验证落盘失败时 images 与 prompt 仍正常返回(落盘是 non-fatal)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lishuceo's task in 1m 42s —— View job


Claude PR Review — ✅ Approved

Verdict: 新增 commit (dd5cc0b) 补齐了上一轮 review 提到的测试缺口。无新增问题。

  • Check previous review comments status (no open inline comments — earlier r3254304805 已 fixed + resolved)
  • Read CLAUDE.md & PR diff
  • Review latest changes (commit dd5cc0b — quoted image 落盘测试)
  • Post review summary

本轮新增 commit 验证 — dd5cc0b test: 覆盖 quoted 图片落盘行为

精准回应上一轮的非阻塞观察「测试侧未新增 injectQuotedMessage 返回 savedImagePath 的集成断言」。新增 src/feishu/__tests__/inject-quoted-message.test.ts 中两个针对落盘的用例:

  • persists quoted image to cache and returns savedImagePath (src/feishu/__tests__/inject-quoted-message.test.ts:203) — 断言 saveMessageFileToCache 被以正确的 (messageId, imageKey, buf, image.png) 调用,返回值传入 savedImagePath;多模态 images 仍正常返回 1 条,确认落盘与压缩解耦。
  • returns no savedImagePath when cache save throws (non-fatal) (src/feishu/__tests__/inject-quoted-message.test.ts:222) — 断言落盘失败时主流程不被阻塞:images 仍 1 条、prompt 仍含「引用了一张图片」、savedImagePathundefined,与生产代码 logger.debug 兜底分支一致 (src/feishu/event-handler.ts:2015-2022)。

测试结构合理:

  • 使用 vi.mock('../file-cache.js', async (importOriginal)) 做 partial mock,只替换 saveMessageFileToCache,保留模块其他实现 — 与项目内已有 mock 模式一致。
  • 注入点选用 _testing.injectQuotedMessage 导出(src/feishu/event-handler.ts:3742),避免穿透整个 executeClaudeTask,单测粒度合适。
  • 现有 11 个 case 覆盖:undefined rootId / rootId==messageId / text / post / merge_forward / 空数组 / API throw / image 成功 / image download 失败 / 落盘成功 / 落盘失败 / existing images 合并 — 覆盖率充分。

设计 / 实现 Highlight(本轮)

  • 测试 commit 单一职责、纯 test 改动、零生产代码变更,复核成本极低。
  • mock buffer 使用真实 PNG magic bytes [0x89, 0x50, 0x4E, 0x47, ...],与 detectImageMediaType 行为一致,避免假阳性。
  • 落盘失败用 mockRejectedValue 触发 try/catch 分支,与 injectQuotedMessage 内的 logger.debug 兜底完全对称。

整体 PR 评估

8 个文件、+308/-32,三层目标全部满足且互相对齐:

  1. 落盘路径parseMessage (current images)、downloadHistoryImages (history images)、injectQuotedMessage (quoted parent image) 三处全部调用 saveMessageFileToCache,try/catch 兜底统一。
  2. restart 注入formatRestartImageHints 去重保序、空数组短路;executeClaudeTask L2536-2539 在 restart 前拼接 hints;时序正确(quoted 路径在 L2326 收集,formatHints 在 L2535)。
  3. MCP 工具feishu_download_message_image 按 magic bytes 推扩展名、30MB 上限、与 feishuMessageFileTool 对称注册。

测试覆盖:formatRestartImageHints 4 case + injectQuotedMessage 11 case + tools 注册计数同步更新。

次要观察(不阻塞,与上轮一致)

  • mediaTypeToExtsrc/feishu/event-handler.ts:1469)与 image.ts:detectImageExt 是两套近似的类型/魔法字节映射,可统一到 file-cache.ts 或 image utils — 重复但可接受。
  • merge_forward 子消息图片仍未落盘(飞书 API 限制本身不支持下载子消息资源),与 PR 范围一致。
  • E2E 手动验证(PR 描述 to-do [ ])仍未完成;单测层面已经充分。

· Branch: feat/claude-session-8dc376
·

@lishuceo
lishuceo merged commit b3fd4df into main May 17, 2026
4 checks passed
@lishuceo
lishuceo deleted the feat/claude-session-8dc376 branch May 17, 2026 09:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant